package cn.cleanarch.infra.idp.uaa.authentication.config;

import cn.cleanarch.infra.idp.uaa.authentication.exception.BaseSecurityExceptionHandler;
import jakarta.servlet.Filter;
import java.util.List;
import cn.cleanarch.infra.idp.uaa.authentication.exception.CustomAuthenticationExceptionHandler;
import cn.cleanarch.infra.idp.uaa.authentication.exception.CustomAuthorizationExceptionHandler;
import cn.cleanarch.infra.idp.uaa.authentication.login.LoginFailHandler;
import cn.cleanarch.infra.idp.uaa.authentication.login.LoginSuccessHandler;
import cn.cleanarch.infra.idp.uaa.authentication.login.username.UsernameAuthenticationFilter;
import cn.cleanarch.infra.idp.uaa.authentication.login.username.UsernameAuthenticationProvider;
import cn.cleanarch.infra.idp.uaa.authentication.resourceapi.base.ApiAuthenticationFilter;
import cn.cleanarch.infra.idp.uaa.business.service.JwtService;
import cn.cleanarch.infra.idp.pms.constants.ApiPathConstants;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.context.SecurityContextHolderFilter;
import org.springframework.security.web.savedrequest.NullRequestCache;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

@Configuration
@EnableWebSecurity
public class BaseWebSecurityConfig {

  private final ApplicationContext applicationContext;

  public BaseWebSecurityConfig(ApplicationContext applicationContext) {
    this.applicationContext = applicationContext;
  }

  private final AuthenticationEntryPoint authenticationExceptionHandler = new CustomAuthenticationExceptionHandler();
  private final AccessDeniedHandler authorizationExceptionHandler = new CustomAuthorizationExceptionHandler();
  private final Filter globalSpringSecurityExceptionHandler = new BaseSecurityExceptionHandler();

  private void commonHttpSetting(HttpSecurity http) throws Exception {
    http.formLogin(AbstractHttpConfigurer::disable)
        .httpBasic(AbstractHttpConfigurer::disable)
        .logout(AbstractHttpConfigurer::disable)
        .sessionManagement(AbstractHttpConfigurer::disable)
        .csrf(AbstractHttpConfigurer::disable)
        .requestCache(cache -> cache.requestCache(new NullRequestCache()))
        .anonymous(AbstractHttpConfigurer::disable);

    http.exceptionHandling(exceptionHandling ->
        exceptionHandling
            .authenticationEntryPoint(authenticationExceptionHandler)
            .accessDeniedHandler(authorizationExceptionHandler)
    );
    http.addFilterBefore(globalSpringSecurityExceptionHandler, SecurityContextHolderFilter.class);
  }

  @Bean
  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }

  /** 公开端点 - 无需认证 */
  @Bean
  @Order(1)
  public SecurityFilterChain publicFilterChain(HttpSecurity http) throws Exception {
    commonHttpSetting(http);
    http.securityMatcher(
        ApiPathConstants.API_AUTH_CAPTCHA, ApiPathConstants.SECURE_PREVIEW_FILE_PATTERN, ApiPathConstants.SECURE_PREVIEW_VERSION_PATTERN
    )
        .authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll())
        .csrf(csrf -> csrf.disable());
    return http.build();
  }

  /** 登录端点 */
  @Bean
  @Order(2)
  public SecurityFilterChain loginFilterChain(HttpSecurity http) throws Exception {
    commonHttpSetting(http);

    http.securityMatcher(ApiPathConstants.API_AUTH_LOGIN)
        .authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated());

    LoginSuccessHandler successHandler = applicationContext.getBean(LoginSuccessHandler.class);
    LoginFailHandler failHandler = applicationContext.getBean(LoginFailHandler.class);

    UsernameAuthenticationFilter usernameLoginFilter = new UsernameAuthenticationFilter(
        new AntPathRequestMatcher(ApiPathConstants.API_AUTH_LOGIN, HttpMethod.POST.name()),
        new ProviderManager(
            List.of(applicationContext.getBean(UsernameAuthenticationProvider.class))),
        successHandler, failHandler);
    http.addFilterBefore(usernameLoginFilter, UsernamePasswordAuthenticationFilter.class);

    return http.build();
  }

  /** 其他API端点 - 需要认证 */
  @Bean
  @Order(100)
  public SecurityFilterChain defaultApiFilterChain(HttpSecurity http) throws Exception {
    commonHttpSetting(http);
    http.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated());

    http.addFilterBefore(new ApiAuthenticationFilter(applicationContext.getBean(JwtService.class)),
        UsernamePasswordAuthenticationFilter.class);
    return http.build();
  }
}
